Drawing



Cegal Tools package for loading and visualising well log data

An geoscience tool for loading, plotting and evaluating well log data using python 🐍




The Cegal Tools package aims to minimize time and effort for a geoscientist wanting to work with well logs using python.

Based on open source tools such as plotly, pandas and lasio, Cegal Tools allow for simple loading, manipulation and visualising of well logs from las files.

Several built in plotting methods provides an easy to use, out of the box well log tool for geoscientists using or wanting to learn python.



Cegal well tool package written by Hilde Tveit Håland & Thomas Bartholomew Grant, Cegal ASA, May 2020.

License: BSD-3-Clause


Notebook content:




Using the well plotter from the Cegal Tools package


Installing cegal tools package:

  • !pip install cegaltools

The purpose of Cegal Tools Plotting is create a quick and easy way to QC well logs in a jupyter notebook. It's built using plotly, so run in a different IDEs html plots will launch in your default browser.


In [1]:
from cegaltools.plotting import CegalWellPlotter as cwp

Well log viewer

We'll borrow a well log from the Force 2020 Machine Learning Hackathon to demonstrate plotting.

In [2]:
import pandas as pd
In [3]:
well = pd.read_csv('ForceHackathonWell.csv')
In [4]:
well.head()
Out[4]:
DEPTH_MD X_LOC Y_LOC Z_LOC GROUP FORMATION CALI RSHA RMED RDEP ... ROP DTS DCAL DRHO MUDWEIGHT RMIC ROPA RXO FORCE_2020_LITHOFACIES_LITHOLOGY FORCE_2020_LITHOFACIES_CONFIDENCE
0 734.419199 475279.15625 6518414.0 -710.414062 NORDLAND GP. NaN 12.787381 NaN 1.407795 1.472684 ... 26.911829 NaN NaN NaN NaN NaN 30.690878 NaN 65000 1.0
1 734.571199 475279.15625 6518414.0 -710.566101 NORDLAND GP. NaN 12.790987 NaN 1.394810 1.464091 ... 26.133530 NaN NaN NaN NaN NaN 29.542898 NaN 65000 1.0
2 734.723199 475279.15625 6518414.0 -710.718079 NORDLAND GP. NaN 12.801331 NaN 1.377496 1.434632 ... 27.482635 NaN NaN NaN NaN NaN 28.394918 NaN 65000 1.0
3 734.875199 475279.15625 6518414.0 -710.870056 NORDLAND GP. NaN 12.628396 NaN 1.369218 1.419665 ... 28.694523 NaN NaN NaN NaN NaN 27.246870 NaN 65000 1.0
4 735.027199 475279.15625 6518414.0 -711.022095 NORDLAND GP. NaN 12.528492 NaN 1.373198 1.427742 ... 28.981676 NaN NaN NaN NaN NaN 26.563250 NaN 65000 1.0

5 rows × 28 columns


The well plotter assumes the index of the dataframe to be the depth curve, so we will set it to index for our well dataframe:

In [5]:
well.set_index('DEPTH_MD', inplace=True)

We'll print the dataframe columns to get an overview of the logs:

In [6]:
well.columns
Out[6]:
Index(['X_LOC', 'Y_LOC', 'Z_LOC', 'GROUP', 'FORMATION', 'CALI', 'RSHA', 'RMED',
       'RDEP', 'RHOB', 'GR', 'SGR', 'NPHI', 'PEF', 'DTC', 'SP', 'BS', 'ROP',
       'DTS', 'DCAL', 'DRHO', 'MUDWEIGHT', 'RMIC', 'ROPA', 'RXO',
       'FORCE_2020_LITHOFACIES_LITHOLOGY',
       'FORCE_2020_LITHOFACIES_CONFIDENCE'],
      dtype='object')

Now we're ready to input the data with our log classes!



The Well log viewer:

In [7]:
help(cwp.plot_logs)
Help on function plot_logs in module cegaltools.plotting:

plot_logs(df, logs=None, log_scale_logs=None, lithology_logs=None, lithology_proba_logs=None, lithology_description=None, show_fig=True)
    :param df: cegaltools log dataframe
    :param logs: list of logs to plot as line plots
    :param log_scale_logs=None : list og logs to be plotted on a logartihmic scale
    :param lithology_logs: list of lithology logs to be plotted as heatmaps
    :param lithology_proba_logs: list of lithology probability logs to be plotted as black/grey heatmaps
    :param lithology_description: A dictionary containing name and color information for lithology logs
    :param show_fig: show (True) or return (False) figure
    :return: None
    
    A lihtology description is a dictionary with lithology log values as keys and a tuples containing lithology
    name and colour (Hex Code/html name):
            lithology_value_3: ('lst', '#bb4cd0'),
    
            example dict:
                lithology_dict = {
                                    1.0: ('sand', '#e6e208'),
                                    2.0: ('shale', '#0b8102'),
                                    3.0: ('lst', 'Orange'),
                                }

In [8]:
cwp.plot_logs(df=well, 
              logs=['GROUP','FORMATION', 'RHOB', 'GR', 'NPHI', 'DTC', 'DTS'], 
              log_scale_logs=['RMED', 'RDEP'],
              lithology_logs='FORCE_2020_LITHOFACIES_LITHOLOGY', 
              lithology_proba_logs='FORCE_2020_LITHOFACIES_CONFIDENCE')

You can zoom and pan in the plot by suign the toolbar in the top left corner. Zooming along the y-axis zooms is linked for all log tracks. Hit the house symbol in the toolbar to reset the view.

The output plot is a plotly figure, if the show_fig parameter is set to false the function will return the plotly figure instead of yielding a plot.



Passing no values for the four log types will simply plot all logs as normal scale line plots:

In [9]:
cwp.plot_logs(df=well)



Correlation plot

We can use the plot_correlation function to get a simple correlation plot of all input logs:

In [10]:
help(cwp.plot_correlation)
Help on function plot_correlation in module cegaltools.plotting:

plot_correlation(df, show_fig=True)
    :return: None
    
    Creates plotly correlation plot of all present cegaltools logs

In [11]:
cwp.plot_correlation(df=well)

Coverage plot

The coverage plot returns a bar chart with the inverse of missing values as a fraction, ie giving an overview of data coverage for the log set:

In [12]:
help(cwp.plot_coverage)
Help on function plot_coverage in module cegaltools.plotting:

plot_coverage(df, show_fig=True)
    :param show_fig: returns plotly figure if set to False, shows figure if set to true (default True)
    :return:
    
    Plot shows the inverse fraction of missing data, i.e data coverage for individual logs

In [13]:
cwp.plot_coverage(df=well)

Drawing

  ❤️ Data Science



Creating a Well object using the Cegal Tools


  • You can create a well object either by reading a las file (using lasio to do the heavy lifting), or inputing a dataframe containing well log data.

  • Beware that creating a Well object from a dataframe requires that you set the desired depth column as index, if not the well object will not behave as expected.

In [14]:
from cegaltools.wells import Well


Let's make a well object from a Volve well las file. Volve data has been released as an open dataset by Equinor.


A Well object needs at minimum a filename, and if not located in the current directoy, a path to the file:

In [15]:
force_well = Well(filename='ForceHackathonWell.las', path=None)


There are four attributes on the well objects:

  • path : input
  • filename : input
  • well_object : lasio object
  • id : sha224 hash generated for well object
In [16]:
force_well.__dict__
Out[16]:
{'path': '',
 'filename': 'ForceHackathonWell.las',
 'well_object': <lasio.las.LASFile at 0x1902a0941c0>,
 'id': 'ddb49e54ffc6b02e4043025647809060a2dba1c491f59e927ae99dd1'}

Lasio is used to read the las file, by accessing the well_object attribute you can work with and edit the well log file as per the excellent lasio project and documentation.



If you have well log data as a dataframe you can create a Well object by passing the dataframe instead of a filename and setting there parameter from_dataframe to True.

You also have the option of passing a well name, this will be added to the las file header values if you save the Well object to a las file:

In [17]:
dataframe = pd.read_csv('ForceHackathonWell.csv')

dataframe.set_index('DEPTH_MD', inplace=True)
In [18]:
force_well_from_dataframe = Well(filename=dataframe, from_dataframe=True, dataframe_name='my_well')
Well objects will assume the first dataframe column as depth curve, please assert that the passed dataframe has the correct column order
In [19]:
force_well_from_dataframe.__dict__
Out[19]:
{'path': '',
 'filename': None,
 'well_object': <lasio.las.LASFile at 0x1902971c250>,
 'id': '9643c24ac7d571dc3bac1a8eab85410600bed64d96a4c532694baee9'}



You can return a pandas dataframe of the well object using the built in .df() method:

In [20]:
force_well.df()
Out[20]:
X_LOC Y_LOC Z_LOC CALI RSHA RMED RDEP RHOB GR SGR ... ROP DTS DCAL DRHO MUDWEIGHT RMIC ROPA RXO FORCE_2020_LITHOFACIES_LITHOLOGY FORCE_2020_LITHOFACIES_CONFIDENCE
DEPTH_MD
734.4192 475279.15625 6518414.0 -710.41406 12.78738 NaN 1.40780 1.47268 NaN 118.99299 NaN ... 26.91183 NaN NaN NaN NaN NaN 30.69088 NaN 65000.0 1.0
734.5712 475279.15625 6518414.0 -710.56610 12.79099 NaN 1.39481 1.46409 NaN 124.51431 NaN ... 26.13353 NaN NaN NaN NaN NaN 29.54290 NaN 65000.0 1.0
734.7232 475279.15625 6518414.0 -710.71808 12.80133 NaN 1.37750 1.43463 NaN 126.77985 NaN ... 27.48264 NaN NaN NaN NaN NaN 28.39492 NaN 65000.0 1.0
734.8752 475279.15625 6518414.0 -710.87006 12.62840 NaN 1.36922 1.41967 NaN 127.18607 NaN ... 28.69452 NaN NaN NaN NaN NaN 27.24687 NaN 65000.0 1.0
735.0272 475279.15625 6518414.0 -711.02209 12.52849 NaN 1.37320 1.42774 NaN 125.18471 NaN ... 28.98168 NaN NaN NaN NaN NaN 26.56325 NaN 65000.0 1.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2361.2752 NaN NaN NaN 8.58836 NaN NaN NaN 2.43101 76.10313 NaN ... 20.66543 NaN NaN -0.00457 NaN NaN 20.73232 NaN 65030.0 2.0
2361.4272 NaN NaN NaN 8.58759 NaN NaN NaN 2.42653 80.99770 NaN ... 20.68627 NaN NaN -0.00250 NaN NaN 21.06999 NaN 65030.0 2.0
2361.5792 NaN NaN NaN 8.59484 NaN NaN NaN 2.42231 84.44711 NaN ... 20.59132 NaN NaN 0.00050 NaN NaN 21.40779 NaN 65030.0 2.0
2361.7312 NaN NaN NaN 8.59431 NaN NaN NaN 2.42329 86.44938 NaN ... 19.43705 NaN NaN 0.00055 NaN NaN 21.74564 NaN 65030.0 2.0
2361.8832 NaN NaN NaN 8.59120 NaN NaN NaN 2.43534 87.69736 NaN ... 20.99478 NaN NaN -0.00112 NaN NaN 22.08317 NaN 65030.0 2.0

10708 rows × 25 columns

The dataframe can be edited and manipulated as per the pandas documentation.

Note that if you want to write edited columns back to las format you well need to add it with the add_to_well() method.




Built in plots for the Well object


The Cegal Well Plotter plots are also available as methods for the Well object:

In [21]:
force_well.plot_logs(logs=['RHOB', 'GR', 'NPHI', 'DTC', 'DTS'], 
                      log_scale_logs=['RMED', 'RDEP'],
                      lithology_logs='FORCE_2020_LITHOFACIES_LITHOLOGY', 
                      lithology_proba_logs='FORCE_2020_LITHOFACIES_CONFIDENCE')

The Well object also has the option to print a summary report for the well data:

In [22]:
force_well.report()



Adding logs and writing Well object as las file



Adding a new log

Adding logs to a Well object is done indirectly via lasios insert curve function, however the Well object requires the Well object id (sha244 hash generated for the Well object) to be passed together with new curve. The purpose for this is to assure that logs are written to the correct Well object if called from functions or in loops etc.

The new curve should be passed as a tuple with the Well object id:

(Well_object.id, new_curve)

The well id for our test well is printed below:

In [23]:
force_well.id
Out[23]:
'ddb49e54ffc6b02e4043025647809060a2dba1c491f59e927ae99dd1'


We'll write a quick function that let's us estimate the Steiber estimation for VShale.

Notice that we pass the well object and not the well dataframe to the function, that allows us to return the well id for the well the VSH estimation is created from together with the calculated values:

In [24]:
def create_Steiber_VSH_estimation(well_object):
    linear_values = ((well_object.df()['GR'] - well_object.df()['GR'].min()) / 
                     (well_object.df()['GR'].max() - well_object.df()['GR'].min()))

    return (well_object.id, linear_values / (3 - (2*linear_values)))
In [25]:
create_Steiber_VSH_estimation(force_well)
Out[25]:
('ddb49e54ffc6b02e4043025647809060a2dba1c491f59e927ae99dd1',
 DEPTH_MD
 734.4192     0.356985
 734.5712     0.389611
 734.7232     0.403728
 734.8752     0.406306
 735.0272     0.393742
                ...   
 2361.2752    0.166975
 2361.4272    0.184290
 2361.5792    0.197042
 2361.7312    0.204664
 2361.8832    0.209500
 Name: GR, Length: 10708, dtype: float64)


We can add the output to the well object, and at the same time specify a name for the new log:

In [26]:
force_well.add_to_well(create_Steiber_VSH_estimation(force_well), log_name='Steiber_VSH')
In [27]:
force_well.plot_logs(logs=['GR','Steiber_VSH'])



Writing Well object to las file


To save the Well object with the added curve back to a las file we can simply call write_las on the object, while providing a name for the file to be written. The file will be saved in the current directory:

In [28]:
force_well.write_las(filename='our_edited_force_well')


Easy as 🥧




Drawing

  ❤️ Data Science